package hole

import (
	"strconv"
	"strings"
)

type GridTestCase struct {
	width int
	boxes []GridBox
}

type GridBox struct{ width, height int }

type GridBoxSolution struct{ x, y int }

type GridSolution []GridBoxSolution

func formatGridSolution(solution GridSolution) string {
	var sb strings.Builder

	for i, box := range solution {
		sb.WriteString(strconv.Itoa(box.x))
		sb.WriteByte(',')
		sb.WriteString(strconv.Itoa(box.y))

		if i < len(solution)-1 {
			sb.WriteByte(' ')
		}
	}

	return sb.String()
}

func parseInt(k string) int {
	out, err := strconv.Atoi(k)
	if err != nil {
		panic(err)
	}
	return out
}

func parseGridQuestion(data string) GridTestCase {
	parts := strings.Split(data, " ")

	boxes := make([]GridBox, 0)
	for i := range len(parts) - 1 {
		coords := strings.Split(parts[i+1], ",")
		boxes = append(boxes, GridBox{
			width:  parseInt(coords[0]),
			height: parseInt(coords[1]),
		})
	}

	return GridTestCase{
		width: parseInt(parts[0]),
		boxes: boxes,
	}
}

func formatGridQuestion(board GridTestCase) string {
	var sb strings.Builder

	sb.WriteString(strconv.Itoa(board.width))
	sb.WriteByte(' ')

	for i, box := range board.boxes {
		sb.WriteString(strconv.Itoa(box.width))
		sb.WriteByte(',')
		sb.WriteString(strconv.Itoa(box.height))
		if i < len(board.boxes)-1 {
			sb.WriteByte(' ')
		}
	}

	return sb.String()
}

func All[T any](ts []T, pred func(T) bool) bool {
	for _, t := range ts {
		if !pred(t) {
			return false
		}
	}
	return true
}

func solveGrid(question GridTestCase) GridSolution {
	heights := make([]int, question.width)
	solution := make(GridSolution, len(question.boxes))
	var x, y int

	for i, box := range question.boxes {
		for x+box.width > question.width || !All(heights[x:x+box.width],
			func(k int) bool { return k <= y },
		) {
			x++
			if x+box.width > question.width {
				x = 0
				y++
			}
		}

		for j := range box.width {
			heights[j+x] = y + box.height
		}
		solution[i] = GridBoxSolution{x, y}
		x += box.width
	}

	return solution
}

func getGridStaticTestCases() (grids []GridTestCase) {
	for _, test := range fixedTests("grid-packing") {
		grids = append(grids, parseGridQuestion(test.in))
	}

	return shuffle(grids)
}

func genGridTestCase() GridTestCase {
	width := randInt(1, 15)
	count := randInt(5, 45)
	boxes := make([]GridBox, count)

	for i := range count {
		boxes[i] = GridBox{
			width:  min(randInt(1, width), randInt(1, width)),
			height: min(randInt(1, 10), randInt(1, 10)),
		}
	}

	return GridTestCase{width, boxes}
}

func getGridAutogeneratedTestCases() []GridTestCase {
	grids := make([]GridTestCase, 20)

	for i := range grids {
		grids[i] = genGridTestCase()
	}

	return grids
}

var _ = answerFunc("css-grid", func() []Answer {
	runs := make([][]test, 2)

	testCases := [][]GridTestCase{
		getGridAutogeneratedTestCases(),
		getGridStaticTestCases(),
	}

	for i, board := range testCases {
		runs[i] = make([]test, len(board))

		for j, grid := range board {
			runs[i][j].in = formatGridQuestion(grid)
			runs[i][j].out = formatGridSolution(solveGrid(grid))
		}
	}

	return outputTests(runs...)
})
